/*
 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#pragma once

#include "Common.hpp"
#include "Line.hpp"

namespace Brisk {

class Bezier {
public:
    Bezier() = default;
    PointF pointAt(float t) const;
    float angleAt(float t) const;
    Bezier onInterval(float t0, float t1) const;
    float length() const;
    static void coefficients(float t, float& a, float& b, float& c, float& d);
    static Bezier fromPoints(const PointF& start, const PointF& cp1, const PointF& cp2, const PointF& end);
    inline void parameterSplitLeft(float t, Bezier* left);
    inline void split(Bezier* firstHalf, Bezier* secondHalf) const;

    float tAtLength(float len) const {
        return tAtLength(len, length());
    }

    float tAtLength(float len, float totalLength) const;
    void splitAtLength(float len, Bezier* left, Bezier* right);

    PointF pt1() const {
        return { x1, y1 };
    }

    PointF pt2() const {
        return { x2, y2 };
    }

    PointF pt3() const {
        return { x3, y3 };
    }

    PointF pt4() const {
        return { x4, y4 };
    }

private:
    PointF derivative(float t) const;
    float x1, y1, x2, y2, x3, y3, x4, y4;
};

inline void Bezier::coefficients(float t, float& a, float& b, float& c, float& d) {
    float m_t = 1.0f - t;
    b         = m_t * m_t;
    c         = t * t;
    d         = c * t;
    a         = b * m_t;
    b *= 3.0f * t;
    c *= 3.0f * m_t;
}

inline PointF Bezier::pointAt(float t) const {
    // numerically more stable:
    float x, y;

    float m_t = 1.0f - t;
    {
        float a = x1 * m_t + x2 * t;
        float b = x2 * m_t + x3 * t;
        float c = x3 * m_t + x4 * t;
        a       = a * m_t + b * t;
        b       = b * m_t + c * t;
        x       = a * m_t + b * t;
    }
    {
        float a = y1 * m_t + y2 * t;
        float b = y2 * m_t + y3 * t;
        float c = y3 * m_t + y4 * t;
        a       = a * m_t + b * t;
        b       = b * m_t + c * t;
        y       = a * m_t + b * t;
    }
    return { x, y };
}

inline void Bezier::parameterSplitLeft(float t, Bezier* left) {
    left->x1 = x1;
    left->y1 = y1;

    left->x2 = x1 + t * (x2 - x1);
    left->y2 = y1 + t * (y2 - y1);

    left->x3 = x2 + t * (x3 - x2); // temporary holding spot
    left->y3 = y2 + t * (y3 - y2); // temporary holding spot

    x3       = x3 + t * (x4 - x3);
    y3       = y3 + t * (y4 - y3);

    x2       = left->x3 + t * (x3 - left->x3);
    y2       = left->y3 + t * (y3 - left->y3);

    left->x3 = left->x2 + t * (left->x3 - left->x2);
    left->y3 = left->y2 + t * (left->y3 - left->y2);

    left->x4 = x1 = left->x3 + t * (x2 - left->x3);
    left->y4 = y1 = left->y3 + t * (y2 - left->y3);
}

inline void Bezier::split(Bezier* firstHalf, Bezier* secondHalf) const {
    float c        = (x2 + x3) * 0.5f;
    firstHalf->x2  = (x1 + x2) * 0.5f;
    secondHalf->x3 = (x3 + x4) * 0.5f;
    firstHalf->x1  = x1;
    secondHalf->x4 = x4;
    firstHalf->x3  = (firstHalf->x2 + c) * 0.5f;
    secondHalf->x2 = (secondHalf->x3 + c) * 0.5f;
    firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2) * 0.5f;

    c                              = (y2 + y3) / 2;
    firstHalf->y2                  = (y1 + y2) * 0.5f;
    secondHalf->y3                 = (y3 + y4) * 0.5f;
    firstHalf->y1                  = y1;
    secondHalf->y4                 = y4;
    firstHalf->y3                  = (firstHalf->y2 + c) * 0.5f;
    secondHalf->y2                 = (secondHalf->y3 + c) * 0.5f;
    firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2) * 0.5f;
}
} // namespace Brisk
